#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "defs.h"
#include "Scalar.h"

#include "ply.h"

#ifdef _DEBUG
#define CLICKS_TO_UPDATE        0x1fff
#else 
#define CLICKS_TO_UPDATE        0x1ffff
#endif

static char *_G_err_str = NULL;
static void ( *_G_update_callback)() = NULL;

static void call_update_callback() {
  if ( _G_update_callback)
    _G_update_callback();
}

static void set_err_str( const char *str = NULL) {
  if ( _G_err_str) free( _G_err_str);
  _G_err_str = NULL;
  if ( str) {
    _G_err_str = strdup( str);
  }
}

const char *get_ply_error() { // gets error string if there was any error while reading ply file
  return _G_err_str; 
}

void set_ply_update_callback( void ( *fnc)()) {
  _G_update_callback = fnc;
}

/* user's vertex and face definitions for a polygonal object */

typedef struct Vertex {
  float x,y,z;
  float nx,ny,nz;
  void *other_props;       /* other properties */
} Vertex;

typedef struct Face {
  unsigned char nverts;    /* number of vertex indices in list */
  int *verts;              /* vertex index list */
  void *other_props;       /* other properties */
} Face;

const char *elem_names[] = { /* list of the kinds of elements in the user's object */
  "vertex", "face"
};

PlyProperty vert_props[] = { /* list of property information for a vertex */
  { ( char *)"x", Float32, Float32, offsetof(Vertex,x), 0, 0, 0, 0},
  { ( char *)"y", Float32, Float32, offsetof(Vertex,y), 0, 0, 0, 0},
  { ( char *)"z", Float32, Float32, offsetof(Vertex,z), 0, 0, 0, 0},
  { ( char *)"nx", Float32, Float32, offsetof(Vertex,nx), 0, 0, 0, 0},
  { ( char *)"ny", Float32, Float32, offsetof(Vertex,ny), 0, 0, 0, 0},
  { ( char *)"nz", Float32, Float32, offsetof(Vertex,nz), 0, 0, 0, 0},
};

PlyProperty face_props[] = { /* list of property information for a face */
  { ( char *)"vertex_indices", Int32, Int32, offsetof(Face,verts),
   1, Uint8, Uint8, offsetof(Face,nverts)},
};


/*** the PLY object ***/

static PlyFile *in_ply;
static int nverts,nfaces;
static Vertex **vlist;
static Face **flist;
static PlyOtherProp *vert_other,*face_other;

// static int flip_order = 1;       /* flip order of vertices around the faces? */
// static int flip_normals = 0;     /* flip vertex normals? */

/* are normals in PLY file? */
static int has_nx = 0;
static int has_ny = 0;
static int has_nz = 0;


/******************************************************************************
Read in the PLY file from standard in.
******************************************************************************/

int read_ply_file( const char *filename) {
  int i,j;
  int elem_count;
  char *elem_name;
  
  /*** Read in the original PLY object ***/
  FILE *f_in = fopen( filename, "rb");
  if ( !f_in)
    return 0;
  in_ply = read_ply( f_in); // close_ply already fclose's f_in

  if ( !in_ply) {
    set_err_str( "Problems reading the file");
    fclose( f_in);
    return 0;
  }
  
  /* examine each element type that is in the file (vertex, face) */

  for (i = 0; i < in_ply->num_elem_types; i++) {

    call_update_callback();
    
    /* prepare to read the i'th list of elements */
    elem_name = setup_element_read_ply (in_ply, i, &elem_count);
    if ( elem_name == NULL) {
      set_err_str( "Problems reading the file");
      close_ply (in_ply);
      free_ply( in_ply);
      in_ply = NULL;
      return 0;
    }
    if (equal_strings ("vertex", elem_name)) {

      /* create a vertex list to hold all the vertices */
      vlist = (Vertex **) malloc (sizeof (Vertex *) * elem_count);
      nverts = elem_count;

      /* set up for getting vertex elements */
      /* (we want x,y,z) */

      setup_property_ply (in_ply, &vert_props[0]);
      setup_property_ply (in_ply, &vert_props[1]);
      setup_property_ply (in_ply, &vert_props[2]);

      /* we also want normal information if it is there (nx,ny,nz) */

      for (j = 0; j < in_ply->elems[i]->nprops; j++) {
	PlyProperty *prop;
	prop = in_ply->elems[i]->props[j];
	if (equal_strings ("nx", prop->name)) {
	  setup_property_ply (in_ply, &vert_props[3]);
	  has_nx = 1;
	}
	if (equal_strings ("ny", prop->name)) {
	  setup_property_ply (in_ply, &vert_props[4]);
	  has_ny = 1;
	}
	if (equal_strings ("nz", prop->name)) {
	  setup_property_ply (in_ply, &vert_props[5]);
	  has_nz = 1;
	}
      }

      /* also grab anything else that we don't need to know about */

      vert_other = get_other_properties_ply (in_ply, 
					     offsetof(Vertex,other_props));

      /* grab the vertex elements and store them in our list */

      for (j = 0; j < elem_count; j++) {
        vlist[j] = (Vertex *) malloc (sizeof (Vertex));
        if ( get_element_ply (in_ply, (void *) vlist[j]) == -1) {
          set_err_str( "Problems reading vertices of the file");
          close_ply (in_ply);
          free_ply( in_ply);
          in_ply = NULL;
          return 0;
        }
        if ( !( j & CLICKS_TO_UPDATE)) {
          call_update_callback();
        }
      }
    }
    else if (equal_strings ("face", elem_name)) {

      /* create a list to hold all the face elements */
      flist = (Face **) malloc (sizeof (Face *) * elem_count);
      nfaces = elem_count;

      /* set up for getting face elements */
      /* (all we need are vertex indices) */

      setup_property_ply (in_ply, &face_props[0]);
      face_other = get_other_properties_ply (in_ply, 
					     offsetof(Face,other_props));

      /* grab all the face elements and place them in our list */

      for (j = 0; j < elem_count; j++) {
        flist[j] = (Face *) malloc (sizeof (Face));
        if ( get_element_ply (in_ply, (void *) flist[j]) == -1) {
          set_err_str( "Problems reading faces of the file");
          close_ply (in_ply);
          free_ply( in_ply);
          in_ply = NULL;
          return 0;
        }
        if ( !( j & CLICKS_TO_UPDATE)) {
          call_update_callback();
        }
      }
    }
    else  /* all non-vertex and non-face elements are grabbed here */
      get_other_element_ply (in_ply);
  }

  /* close the file */
  /* (we won't free up the memory for in_ply because we will use it */
  /*  to help describe the file that we will write out) */

  close_ply (in_ply);
  free_ply( in_ply);
  in_ply = NULL;
  return nverts;
}

template< int N>
int *get_connectivity( int num_elements, Face **lst_faces, int n_faces) {
  int *connectivity = ( int *)malloc( num_elements * N * sizeof( int));
  int idx_connec = 0;
  for ( int i = 0 ; i < n_faces; i++) {
    if ( lst_faces[ i]->nverts == N) {
      for ( int j = 0; j < N; j++) {
        connectivity[ idx_connec++] = lst_faces[ i]->verts[ j];
      }
    }
  }
  return connectivity;
}

// #define StartType  0
// #define Int8       1
// #define Int16      2
// #define Int32      3
// #define Uint8      4
// #define Uint16     5
// #define Uint32     6
// #define Float32    7
// #define Float64    8
// #define EndType    9
static float plyType2Float( unsigned char *data, int ply_type) {
  float ret = 0.0;
  switch( ply_type) {
    case Int8:
      ret = ( float)*( ( char *)data);
      break;
    case Int16:
      ret = ( float)*( ( short int *)data);
      break;
    case Int32:
      ret = ( float)*( ( int *)data);
      break;
    case Uint8:
      ret = ( float)*( ( unsigned char *)data);
      break;
    case Uint16:
      ret = ( float)*( ( unsigned short int *)data);
      break;
    case Uint32:
      ret = ( float)*( ( unsigned int *)data);
      break;
    case Float32:
      ret = *( ( float *)data);
      break;
    case Float64:
      ret = ( float)*( ( double *)data);
      break;
  }
  return ret;
}

int read_ply_file( const char *filename,
                   int *num_points,
                   int *num_lines,
                   int *num_triangles,
                   int *num_quads,
                   float **lst_points_out, // pointer to a float * to store num_points * 3 coordinates
                   int **lst_lines_out, // pointer to a int * to store num_lines * 2 nodes
                   int **lst_triangles_out, // pointer to a int * to store num_triangles * 3 nodes
                   int **lst_quads_out, // pointer to a int * to store num_quads * 4 nodes
                   float **lst_normals_out, // pointer to a float * to store num_points * 3 normals coordinates
                   float **lst_scalar_out) { // pointer to a float * to store num_points * 1 intensity
  const char *name = strrchr( filename, '/');
#ifdef _WIN32
  if ( !name) name = strrchr( filename, '\\');
#endif // _WIN32
  if ( !name) name = filename;
  else name++;

  int ret = read_ply_file( filename);

  *num_points = 0;
  *num_lines = 0;
  *num_triangles = 0;
  *num_quads = 0;
  *lst_points_out = NULL;
  *lst_lines_out = NULL;
  *lst_triangles_out = NULL;
  *lst_quads_out = NULL;
  *lst_normals_out = NULL;
  *lst_scalar_out = NULL;
  
  int err = 1;
  if ( ret && nverts && nfaces) {
    err = 1;
    float *lst_vertices = ( float *)malloc( 3 * nverts * sizeof( float));
    int idx_v = 0;
    for ( int i = 0; i < nverts; i++) {
      lst_vertices[ idx_v++] = vlist[ i]->x;
      lst_vertices[ idx_v++] = vlist[ i]->y;
      lst_vertices[ idx_v++] = vlist[ i]->z;
    }
    *lst_points_out = lst_vertices;
    *num_points = nverts;
    
    int num_elements[ 11] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    for ( int i = 0; i < nfaces; i++) {
      int num_verts = flist[ i]->nverts;
      if ( num_verts > 10) num_verts = 10;
      num_elements[ num_verts]++;
    }
    // create lines:
    if ( num_elements[ 2]) {
      int *lines = get_connectivity< 2>( num_elements[ 2], flist, nfaces);
      *num_lines = num_elements[ 2];
      *lst_lines_out = lines;
    }
    // create triangles:
    if ( num_elements[ 3]) {
      int *triangles = get_connectivity< 3>( num_elements[ 3], flist, nfaces);
      *num_triangles = num_elements[ 3];
      *lst_triangles_out = triangles;
    }
    // create quadrilaterals:
    if ( num_elements[ 4]) {
      int *quads = get_connectivity< 4>( num_elements[ 4], flist, nfaces);
      *num_quads = num_elements[ 4];
      *lst_quads_out = quads;
    }
    
    // now the other properties:
    if ( has_nx && has_ny && has_nz) {
      float *lst_normals = ( float *)malloc( 3 * nverts * sizeof( float));
      int idx_v = 0;
      for ( int i = 0; i < nverts; i++) {
        lst_normals[ idx_v++] = vlist[ i]->nx;
        lst_normals[ idx_v++] = vlist[ i]->ny;
        lst_normals[ idx_v++] = vlist[ i]->nz;
      }
      *lst_normals_out = lst_normals;
    }

    bool both_properties = vert_other && vert_other->nprops && face_other && face_other->nprops;
    if ( vert_other && vert_other->nprops) {
      float *lst_prop = ( float *)malloc( nverts * sizeof( float));
      size_t offset_ply_types[ EndType] = { 0, 1, 2, 4, 1, 2, 4, 4, 8};
      size_t offset_prop = 0;
      char tmp_name[ 8192];
      for ( int iprop = 0; iprop < vert_other->nprops; iprop++) {
        PlyProperty *cur_prop = vert_other->props[ iprop];
        if( both_properties) {
          sprintf( tmp_name, "vertex//%s", cur_prop->name);
        } else {
          strcpy( tmp_name, cur_prop->name);
        }
        for ( int i = 0; i < nverts; i++) {
          unsigned char *ptr_cur_prop = ( unsigned char *)( vlist[ i]->other_props) + cur_prop->offset; // offset_prop;
          lst_prop[ i] = plyType2Float( ptr_cur_prop, cur_prop->internal_type);
        }
        *lst_scalar_out = lst_prop;
        // break; // only first scalar
        offset_prop += offset_ply_types[ cur_prop->internal_type];
      }
      if ( !*lst_scalar_out)
        free( lst_prop);
    }
    
    if ( face_other && face_other->nprops) {
      // float *lst_prop = ( float *)malloc( nfaces * sizeof( float));
      // size_t offset_ply_types[ EndType] = { 0, 1, 2, 4, 1, 2, 4, 4, 8};
      // size_t offset_prop = 0;
      // char tmp_name[ 8192];
      // for ( int iprop = 0; iprop < face_other->nprops; iprop++) {
      //   PlyProperty *cur_prop = face_other->props[ iprop];
      //   if( both_properties) {
      //     sprintf( tmp_name, "face//%s", cur_prop->name);
      //   } else {
      //     strcpy( tmp_name, cur_prop->name);
      //   }
      //   int res_id = GiD_NewResult( "Geometry", 0.0, tmp_name, 0);
      //   for ( int i = 0; i < nfaces; i++) {
      //     unsigned char *ptr_cur_prop = ( unsigned char *)( flist[ i]->other_props) + cur_prop->offset; // offset_prop;
      //     lst_prop[ i] = plyType2Float( ptr_cur_prop, cur_prop->internal_type);
      //     // if ( ptr_cur_prop) free( ptr_cur_prop); // free local other_prop
      //   }
      //   GiD_SetResultPointer( res_id, GIDPOST_FLOAT, GIDPOST_RESULTS, GIDPOST_SCALAR, GIDPOST_ELEMENTAL, nfaces, 0, lst_prop);
      //   GiD_EndResult( res_id);
      //   offset_prop += offset_ply_types[ cur_prop->internal_type];
      //   // if ( cur_prop) free( cur_prop); // free other_prop
      // }
      // free( lst_prop);
    }
    
    // liberamos memoria:
    
    for ( int i = 0; i < nverts; i++) {
      if ( vert_other && vert_other->nprops) {
        free( vlist[ i]->other_props);
      }
      free( vlist[ i]);
      if ( !( i & CLICKS_TO_UPDATE)) {
        call_update_callback();
      }
    }
    free( vlist);
    
    call_update_callback();
    
    if ( vert_other) {
      PlyProperty *cur_prop = NULL;
      for ( int ip = 0; ip < vert_other->nprops; ip++) {
	cur_prop = vert_other->props[ ip];
	if ( cur_prop) {
	  if ( cur_prop->name) {
	    free( cur_prop->name);
	  }
	  free( cur_prop);
	}
      }
      if ( vert_other->name) {
	free( vert_other->name);
	vert_other->name = NULL;
      }
      free( vert_other->props);
      vert_other->props = NULL;
      free( vert_other);
      vert_other = NULL;
    }

    call_update_callback();
    
    for ( int i = 0; i < nfaces; i++) {
      free( flist[ i]->verts); // they are internally allocated inside ply.c
      if ( face_other && face_other->nprops) {
        free( flist[ i]->other_props);
      }
      free( flist[ i]);
      if ( !( i & CLICKS_TO_UPDATE)) {
        call_update_callback();
      }
    }
    free( flist);
    
    call_update_callback();
    
    if ( face_other) {
      PlyProperty *cur_prop = NULL;
      for ( int ip = 0; ip < face_other->nprops; ip++) {
	cur_prop = face_other->props[ ip];
	if ( cur_prop) {
	  if ( cur_prop->name) {
	    free( cur_prop->name);
	  }
	  free( cur_prop);
	}
      }
      if ( face_other->name) {
	free( face_other->name);
	face_other->name = NULL;
      }
      free( face_other->props);
      face_other->props = NULL;
      free( face_other);
      face_other = NULL;
    }

    call_update_callback();
    
    nverts = 0;
    nfaces = 0;
    
  } else {
    err = 0; // error
  }
  
  return err;
}

#include "Punto.h"
#include "Triangulo.h"

int write_ply_file( const char *filename,
                    const char *obj_name, 
                    int num_puntos, const Punto *lst_puntos,
                    int num_triangulos, const Triangulo *lst_triangulos,
                    const Scalars **lst_scalars) { // last_scalar[ i] should be NULL
  int ret = 0;

  // check extension, should be '.ply'
  char full_name[ 8192];
  strcpy( full_name, filename);
  char *ext_ok = strrchr( full_name, '.');
  if ( ext_ok) {
    if ( strcasecmp( ext_ok, ".ply")) {
      ext_ok = NULL; // if ext but is not .ply
    }
  }
  if ( !ext_ok) {
    strcat( full_name, ".msh"); // add .ply
  }

  FILE *fo = fopen( full_name, "w");
  if ( fo) {
    fprintf( fo, "ply\n");
    fprintf( fo, "format ascii 1.0\n");
    fprintf( fo, "comment object name '%s'\n", obj_name);
    fprintf( fo, "comment generated by gMeshSim\n");
    fprintf( fo, "element vertex %d\n", num_puntos);
    fprintf( fo, "property float x\n");
    fprintf( fo, "property float y\n");
    fprintf( fo, "property float z\n");
    if ( lst_scalars && lst_scalars[ 0]) {
      for ( int idx = 0; lst_scalars[ idx]; idx++) {
        fprintf( fo, "property float %s\n", lst_scalars[ idx]->get_name());
      }
    }
    fprintf( fo, "element face %d\n", num_triangulos);
    fprintf( fo, "property list uchar int vertex_indices\n");
    fprintf( fo, "end_header\n");
    for ( int ip = 0; ip < num_puntos; ip++) {
      fprintf( fo, "%.14g %.14g %.14g", lst_puntos[ ip].x(), lst_puntos[ ip].y(), lst_puntos[ ip].z());
      if ( lst_scalars && lst_scalars[ 0]) {
        for ( int idx = 0; lst_scalars[ idx]; idx++) {
          fprintf( fo, " %.14g", ( *( lst_scalars[ idx]))[ ip]);
        }
      }
      fprintf( fo, "\n");
      if ( !( ip & CLICKS_TO_UPDATE)) {
        call_update_callback();
      }
    }
    for ( int it = 0; it < num_triangulos; it++) {
      fprintf( fo, "3 %lld %lld %lld\n", 
	       ( long long int)lst_triangulos[ it].v1() + 1, 
	       ( long long int)lst_triangulos[ it].v2() + 1, 
	       ( long long int)lst_triangulos[ it].v3() + 1);
      if ( !( it & CLICKS_TO_UPDATE)) {
        call_update_callback();
      }
    }
    fclose( fo);
    ret = 1;
  }
  
  return ret;
}

